/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.variable;

import java.util.ArrayList;
import java.util.List;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.chain.ChainNode.MSG;
import tal.drivechain.chain.NodeInfo;
import tal.drivechain.chain.shell.InfoPrinter;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.system.PluginLoader;
import tal.drivechain.module.properties.PropertiesManager;
import tal.drivechain.module.xmlconfig.ModuleInfo;
import tal.drivechain.module.xmlconfig.PropertyInfo;
import tal.drivechain.parsing.ParsingAid;

/**
 *
 * @author Main
 */
public class SetProperty {
    public enum Flag {
        REVERT, // delete/propname - remove the current property value. propValue==null
        DEFAULT, // default/propname - change back to the default value as written in the xml file.
        REPLACE, // replace/propname=propvalue - replace the value of the property to a new one. default flag
        ADD; // add another value to this property name. only for repeatable properties

        public String getName() {
            switch (this) {
                case REVERT: return "revert";
                case DEFAULT: return "default";
                case REPLACE: return "replace";
                case ADD: return "add";
            }
            return null;
        }

        public static Flag parseFlag(String flagName) throws IllegalArgumentException {
           flagName = flagName.trim();
           for (Flag f : Flag.values()) {
               if (flagName.equals(f.getName()))
                   return f;
           }
           throw new IllegalArgumentException("Unknown prop flag name: " + flagName);
        }
    }
    
    private List<Object[]> props = new ArrayList<Object[]>();
    private Flag flag;
    private String propString;
    private NodeInfo nodeInfo;

    public SetProperty(String className, String prop, SetProperty.Flag flag) throws IllegalArgumentException {
        this(className, new Tokenizer(prop, ';').getTokens(), flag);
    }

    public SetProperty(String className, String[] sprops, SetProperty.Flag flag) throws IllegalArgumentException {
        //name/flag({prop; prop; prop})
        this.flag = flag;
        this.propString = createPropString(sprops);
        if (className.equals("Chain")) { // will set a chain property
            for (String sprop : sprops) {
                int colonIdx = ParsingAid.indexOfUnnested(sprop, ':', "{(['", "})]'");
                String propName = sprop.substring(0, colonIdx).trim();
                String propVal = sprop.substring(colonIdx+1).trim();
                props.add(new Object[] {propName, propVal});
            }
        } else {
            nodeInfo = PluginLoader.getModuleInfo(className);
        
            // create an property parser:
            PropertiesManager parser = ((ModuleInfo)nodeInfo).createPropertiesManager();
        
        
            //parse property: { prop; prop; prop }
            for (String sprop : sprops) {
                String propName;
                PropertyInfo propInfo = null;
                Object propValue;
            
                // get the key
                if (flag == Flag.REVERT || flag == Flag.DEFAULT) {
                    propName = sprop;
                } else {
                    propName = parser.parsePropName(sprop);
                }

                //get the info object of this property
                for(PropertyInfo a : nodeInfo.getProperties()) {
                    if (a.getName().equals(propName)) {
                        propInfo = a;
                        break;
                    }
                }
                if (propInfo==null)
                    throw new IllegalArgumentException("property '" + propName + "' doesn't exist in module " + className);

                checkFlag(flag, propInfo); // check that the flag is compatible with the asrgument

                if (flag == Flag.DEFAULT) {
                    if (propInfo.isOptional()) {
                        propValue = propInfo.getDefaultValue();
                        flag = Flag.REPLACE;
                    } else throw new IllegalArgumentException("Property " + propName + " doesn't have a default value (and is not optional).");
                } else if (flag == Flag.REVERT) {
                    propValue = null;
                } else if (flag == Flag.REPLACE || flag == Flag.ADD) {
                    if (propInfo.isRepeatable()) {
                        List<Object> valueList = new ArrayList<Object>();
                        valueList.add(parser.parseProp(sprop));
                        propValue = valueList;
                    } else propValue = parser.parseProp(sprop);
                } else throw new IllegalArgumentException("Invalid prop flag: " + flag.toString().toLowerCase());
                props.add(new Object[] {propInfo, propValue});
            }
        }
    }

    private String createPropString(String[] props) {
        String ret = "";
        for (String p : props) {
            ret += p + ";";
        }
        return ret;
    }
    
    public Flag getFlag() { return flag; }
    public String getModuleName() { return (nodeInfo!=null?nodeInfo.getName():"Chain"); }
    
    @Override
    public String toString() {
        String ret = "[" + (nodeInfo!=null?nodeInfo.getName():"Chain") + "/" + flag.getName() + "]{";
        String propToString = "<unknown>";
        for (Object[] prop : props) {
            if (nodeInfo!=null)
                propToString = InfoPrinter.printProperty(null, ((PropertyInfo)prop[0]), prop[1]);
            else propToString = propString;
            ret += propToString + ", ";
        }
        return ret.substring(0, ret.length()-2) + "}";
    }

    private void checkFlag(Flag flag, PropertyInfo propInfo) throws IllegalArgumentException {
        switch (flag) {
            case DEFAULT:
                if (!propInfo.isOptional())
                    throw new IllegalArgumentException("Illegal flag default for property " + propInfo.getName());
                break;
            case ADD:
                if (!propInfo.isRepeatable())
                    throw new IllegalArgumentException("Illegal flag add for property " + propInfo.getName());
                break;
        }
    }
    
    public void invokeSetProperty(ChainNode node) {
        for (Object[] prop : props) {
            if (nodeInfo!=null) {
                PropertyInfo p = (PropertyInfo)prop[0];
                Object propValue = prop[1];
                Flag setFlag;
                if (flag == Flag.REVERT) {
                    propValue = node.getOriginalPropValue(p.getName());
                    setFlag = Flag.REPLACE;
                } else setFlag = flag;

                if (node.isPropertyGettable(p.getName())) {
                    Object val = node.getProperty(p.getName());
                    if ((val==null && propValue==null) || (val!=null && val.equals(propValue))) {
                        node.log("Ignoring " + InfoPrinter.printProperty(node, p, propValue) + ". New value and old value are the same", MSG.DEBUG);
                        return;
                    }
                }
                node.setProperty(p.getName(), propValue, setFlag);
            } else {
                // a chain
                node.setProperty((String)prop[0], (String)prop[1], flag);
            }
        } 
    }

    public static Object[] parseSetProperty(String def) {
        SetProperty.Flag flag;
        String plugName, props;

        int parenIdx = ParsingAid.indexOfUnnested(def, '{', "({['", ")}]'");
        if (parenIdx==-1)
            throw new IllegalArgumentException("Invalid prop value: " + def + " (missing opening curly brackets).");
        else if (def.charAt(def.length()-1)!='}')
            throw new IllegalArgumentException("Invalid prop value: " + def + " (missing closing curly brakcets).");
        else if (parenIdx==0)
            throw new IllegalArgumentException("Invalid prop value: " + def + " (missing moduleName and flag).");
        String nameFlag = def.substring(0, parenIdx);
        if (nameFlag.charAt(0)!='[' || Tokenizer.balanced(nameFlag, 0, '[', ']')!=nameFlag.length()-1)
            throw new IllegalArgumentException("Invalid prop value: " + def + " (should be: [moduleName/flag]{properties} )");
        nameFlag = nameFlag.substring(1, nameFlag.length()-1);
        int slashIdx = nameFlag.indexOf('/');

        if (slashIdx==-1) {
            flag = SetProperty.Flag.REPLACE;
            plugName = nameFlag;
        } else {
            flag = SetProperty.Flag.parseFlag(nameFlag.substring(slashIdx+1));
            plugName = nameFlag.substring(0, slashIdx);
        }

        props = def.substring(parenIdx+1, def.length()-1);
        return new Object[] { plugName, new Tokenizer(props, ';').getTokens(), flag };
    }

    public String getPropertyString() { return propString; }
}
